package org.bintrotter.contentmanager;

import java.util.Random;

public class ContentManager {

	private Block root;
	public final int depth;
	private static Random rnd = new Random();
	
	public ContentManager() {
		
		root = new Block();
		depth = Long.SIZE;
	}
	
	public ContentManager(int max_depth) {
		
		root = new Block();
		depth = max_depth;
	}
	
	public ContentManager(int max_depth, Block _root) {
		
		root = new Block(_root,_root);
		depth = max_depth;
	}
	
	public long countNodes() {
		
		return root.countNodes(); 
	}
	
	public long countLeaves() {
		
		return root.countLeaves();
	}
	
	public boolean isSet(long code) {
		
		Block b = root;
		
		for(int bit = depth-1; bit >= 0 && !b.isLeaf(); bit--)
			b = b.child[(int)((code>>bit) & 1)];
		
		return b == Block.Set;
	}
	
	public boolean setBlock(long code, Block sb) throws Exception {
		
		int bit;
		Block b = root;
		Block p = root;
		
		if(code >= Math.pow(2, depth))
			throw new Exception("tree depth " + depth + " not suffifiant to store " + code);
		
		// close as possible with existing nodes.
		for(bit = depth-1; bit >= 0 && !b.isLeaf(); --bit)
			b = (p = b).child[(int)((code>>bit) & 1)];
		
		if(b != sb)
		{
			// need to change something
			if(bit==-1) {
				// already at bottom of tree, just change existing node.
				p.child[(int)(code & 1)] = sb;
				root.optimise();
				root.optimise(); // FIXME: optimise SHOULDNT requite 2 passes !!!!
				
			} else {
				
				// need to add nodes!
				Block dflt = b; // new non-code-path nodes default to old leaf.
				
				for( b = p, ++bit ; bit>0; --bit)
					b = (b.child[(int)((code>>bit) & 1)] = new Block(dflt,dflt));
				
				b.child[(int)(code & 1)] = sb;
			}
		}
		
		return true;
	}
	
	private int score(Block local, Block remote) {
		
	//	if(local != null)
			local = local.optimise();
		
	//	if(remote != null)
			remote = remote.optimise();
		
		if(remote == Block.Nill)
			return 0; // remote has nothing, no point in searching.
		if(local == Block.Set)
			return 0; // local has everything, no point in searching.
		
		if(local == Block.Nill && remote == Block.Set)
			return 3; // remote has EVERYTHING we need. jump on it!
		
		if(local == Block.Nill && remote == null)
			return 2; // remote definatly has atleast SOME of what we need.
		
		if(local == null && remote == Block.Set)
			return 2; // remote definatly has atleast SOME of what we need.
		
		return 1; // remote MIGHT have somthing wee need.
	}
	
	public long randomNeededCodeFrom( ContentManager remote ) {
		

		Block localBlock = this.root;
		Block remotBlock = remote.root; 
		int bit;
		long code=0;
		
		for(bit = depth-1; bit >= 0; --bit) {
			
			// score child branches
			int leftscore  = score(localBlock.child[0], remotBlock.child[0]);
			int rightscore = score(localBlock.child[1], remotBlock.child[1]);
			
			// determine best ddirection
			boolean goleft  = false;
			boolean goright = false;
			if(leftscore == rightscore) {
				if(leftscore > 0) {
					if(rnd.nextBoolean())
						goright = true;
					else
						goleft = true;
				}
			} else if(leftscore > rightscore)
				goleft  = true;
			else
				goright = true;
				
			
			if(goright) {
				// Go right - set the code bit for this depth to 1.
				code |= (1<<bit);
				
				// traverse the tree
				localBlock = localBlock.child[1];
				remotBlock = remotBlock.child[1];
			}
			else if(goleft) {
				// GO left - leave the code bit for this depth at 0.
				
				// traverse the tree.
				localBlock = localBlock.child[0];
				remotBlock = remotBlock.child[0];
			}
			else {
				
				// Failed - it may be possible that we could find a needed block, but its not worth the trouble to find it.
				//	a different peer may better suit our needs.
				return -1;
			}
		}

		return code;
	}
	
	public boolean intersted(ContentManager cm) {
		
		if(cm==null)
			return false;
		
		return root.interested(cm.root);
	}
	
	public boolean set(long code) throws Exception {
		return setBlock(code, Block.Set);
	}
	
	public boolean unset(long code) throws Exception {
		return setBlock(code, Block.Nill);
	}

	public void optimise() {
		
		root.optimise();
	}
	
	public void dump() {
		
		System.out.println("====================");
		root.dump(0);
	}
}

